﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TermMatrix.cs" company="Arizona State University">
//   Copyright (C) 2011-2012 Sukru Tikves and Arizona State University
//   
//   Released under Microsoft Public License (Ms-PL)
//   See http://cipsdata.codeplex.com/license for up to date license 
//   informaiton.
// </copyright>
// <summary>
//   Sparse Term-Term Matrix Type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Cips.DataMining
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Text;

    /// <summary>
    /// Sparse Term-Term Matrix Type.
    /// </summary>
    /// <typeparam name="T">
    /// Keyword type.
    /// </typeparam>
    public class TermMatrix<T>
    {
        /// <summary>
        /// The term dictionary.
        /// </summary>
        readonly TermDictionary<T> dictionary;

        /// <summary>
        /// The document function.
        /// </summary>
        readonly Func<IEnumerable<int>, IEnumerable<Tuple<int, int>>> documentFunction;

        /// <summary>
        /// The individual matrix rows.
        /// </summary>
        readonly Dictionary<int, TermVector<T>> rows = new Dictionary<int, TermVector<T>>();

        /// <summary>
        /// Initializes a new instance of the <see cref="TermMatrix&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="dictionary">The term dictionary.</param>
        /// <param name="documentFunction">The document processing function.</param>
        public TermMatrix(TermDictionary<T> dictionary, Func<IEnumerable<int>, IEnumerable<Tuple<int, int>>> documentFunction)
        {
            this.dictionary = dictionary;
            this.documentFunction = documentFunction;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TermMatrix&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="dictionary">The term dictionary.</param>
        /// <param name="items">The matrix items.</param>
        public TermMatrix(TermDictionary<T> dictionary, IEnumerable<Tuple<T, T, double>> items)
        {
            this.dictionary = dictionary;
            documentFunction = UnsupportedFunction;

            foreach (var tuple in items)
            {
                if (!rows.ContainsKey(dictionary[tuple.Item1]))
                    rows.Add(dictionary[tuple.Item1], new SparseTermVector<T>(dictionary));

                rows[dictionary[tuple.Item1]][tuple.Item2] += tuple.Item3;
            }
        }

        /// <summary>
        /// Gets the dictionary.
        /// </summary>
        public TermDictionary<T> Dictionary
        {
            get { return this.dictionary; }
        }

        /// <summary>
        /// Gets the rows.
        /// </summary>
        public Dictionary<int, TermVector<T>> Rows
        {
            get { return this.rows; }
        }

        /// <summary>
        /// Gets the <see cref="Cips.DataMining.TermVector&lt;T&gt;"/> with the specified row.
        /// </summary>
        /// <param name="row">
        /// The row.
        /// </param>
        /// <returns>
        /// The associated <see cref="TermVector{T}"/>.
        /// </returns>
        public TermVector<T> this[int row]
        {
            get
            {
                if (!rows.ContainsKey(row))
                    rows.Add(row, new SparseTermVector<T>(dictionary));

                return rows[row];
            }
        }

        /// <summary>
        /// Gets the <see cref="Cips.DataMining.TermVector&lt;T&gt;"/> with the specified row.
        /// </summary>
        /// <param name="row">
        /// The row.
        /// </param>
        /// <returns>
        /// The associated <see cref="TermVector{T}"/>.
        /// </returns>
        public TermVector<T> this[T row]
        {
            get { return this[dictionary[row]]; }
        }

        /// <summary>
        /// Multiplies two matrices.
        /// </summary>
        /// <param name="a">The first matrix.</param>
        /// <param name="b">The second matrix.</param>
        /// <returns>
        /// The result of the matrix multiplication.
        /// </returns>
        public static TermMatrix<T> operator *(TermMatrix<T> a, TermMatrix<T> b)
        {
            if (a.dictionary != b.dictionary)
                throw new ArgumentException("Matrices should be from the same dictionary");

            var result = new TermMatrix<T>(a.dictionary, UnsupportedFunction);
            var count = a.dictionary.GetTerms().Count();

            foreach (var row in a.rows)
            {
                result.rows.Add(row.Key, new SparseTermVector<T>(a.dictionary));

                for (var x = 0; x < count; x++)
                    result.rows[row.Key][x] =
                        row.Value.Sum(i => b.rows.ContainsKey(i.Key) ? i.Value * b.rows[i.Key][x] : 0);
            }

            return result;
        }

        /// <summary>
        /// Adds the specified document.
        /// </summary>
        /// <param name="document">The document.</param>
        public void Add(IEnumerable<T> document)
        {
            Add(document.Select(w => dictionary[w]));
        }

        /// <summary>
        /// Adds the specified document.
        /// </summary>
        /// <param name="document">The document.</param>
        public void Add(IEnumerable<int> document)
        {
            foreach (var pair in documentFunction(document))
            {
                if (!rows.ContainsKey(pair.Item1))
                    rows.Add(pair.Item1, new SparseTermVector<T>(dictionary));

                rows[pair.Item1][pair.Item2]++;
            }
        }

        /// <summary>
        /// Returns a normalized copy of this matrix with the Markov network property.
        /// </summary>
        /// <returns>The normalized copy.</returns>
        public TermMatrix<T> MarkovNormalize()
        {
            var copy = new TermMatrix<T>(dictionary, UnsupportedFunction);

            foreach (var row in rows)
                copy.rows.Add(row.Key, row.Value.Normalize());

            return copy;
        }

        /// <summary>
        /// Returns a transposed copy of this matrix.
        /// </summary>
        /// <returns>The transposed copy.</returns>
        public TermMatrix<T> Transpose()
        {
            var copy = new TermMatrix<T>(dictionary, UnsupportedFunction);

            foreach (var row in rows)
                foreach (var col in row.Value)
                    copy[col.Key][row.Key] = col.Value;

            return copy;
        }

        /// <summary>
        /// Returns a filtered copy of this matrix.
        /// </summary>
        /// <param name="filter">
        /// The filter.
        /// </param>
        /// <returns>
        /// The filtered <see cref="TermMatrix{T}"/> instance.
        /// </returns>
        public TermMatrix<T> Filter(Func<SparseTermVector<T>, SparseTermVector<T>> filter)
        {
            var copy = new TermMatrix<T>(dictionary, UnsupportedFunction);

            foreach (var row in rows)
                copy.rows.Add(row.Key, filter((SparseTermVector<T>)row.Value));

            return copy;
        }

        /// <summary>
        /// Throws <see cref="NotSupportedException"/>.
        /// </summary>
        /// <param name="arg">The argument.</param>
        /// <returns>Does not return.</returns>
        static IEnumerable<Tuple<int, int>> UnsupportedFunction(IEnumerable<int> arg)
        {
            throw new NotSupportedException();
        }
    }
}
